//===============================================================================
// Microsoft patterns & practices
// Mobile Client Software Factory - July 2006
//===============================================================================
// Copyright  Microsoft Corporation.  All rights reserved.
// THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY
// OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT
// LIMITED TO THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
// FITNESS FOR A PARTICULAR PURPOSE.
//===============================================================================
// The example companies, organizations, products, domain names,
// e-mail addresses, logos, people, places, and events depicted
// herein are fictitious.  No association with any real company,
// organization, product, domain name, email address, logo, person,
// places, or events is intended or should be inferred.
//===============================================================================

using System;
using System.Collections.Generic;
using System.Text;
using System.Reflection;
using Microsoft.Practices.Mobile.EndpointCatalog;
using System.Net;
using Microsoft.Practices.Mobile.DisconnectedAgent.Properties;

namespace Microsoft.Practices.Mobile.DisconnectedAgent
{
    /// <summary>
    /// Exception to wrap any exception thrown by return callback methods.
    /// </summary>
    public class ReturnCallbackException : Exception
    {
        /// <summary>
        /// Creates an instance of <see cref="ReturnCallbackException"/>
        /// </summary>
        /// <param name="message">Message for the exception.</param>
        public ReturnCallbackException(string message)
            : base(message)
        {
        }

        /// <summary>
        /// Creates an instance of <see cref="ReturnCallbackException"/>
        /// </summary>
        /// <param name="message">Message for the exception.</param>
        /// <param name="innerException">Exception thrown by the return callback method.</param>
        public ReturnCallbackException(string message, Exception innerException)
            : base(message, innerException)
        {
        }
    }

    /// <summary>
    /// Default implementation of IRequestDispatcher.
    /// It's responsible for request dispatching regarding max retries, callbacks methods and ReturnCallbackException.
    /// It invokes the 
    /// </summary>
    public class WebServiceRequestDispatcher : IRequestDispatcher
    {
        private IProxyFactory onlineProxyFactory;

        /// <summary>
        /// This constructor sets the dispatcher to use a new WebServiceProxyFactory created using 
        /// the given catalog.
        /// </summary>
        /// <param name="catalog">
        ///		IEndpointCatalog used to create a new WebServiceProxyFactory.
        ///	</param>
        public WebServiceRequestDispatcher(IEndpointCatalog catalog, IWSCredentialService credentialSvc)
        {
            onlineProxyFactory = (IProxyFactory)new WebServiceProxyFactory(catalog, credentialSvc);
        }

        /// <summary>
        /// This constructor sets the dispatcher to use the given onlineProxyFactory.
        /// </summary>
        /// <param name="onlineProxyFactory">
        ///		IProxyFactory used to create proxy objects.
        /// </param>
        public WebServiceRequestDispatcher(IProxyFactory onlineProxyFactory)
        {
            this.OnlineProxyFactory = onlineProxyFactory;
        }

        /// <summary>
        /// Get/Set the IProxyFactory used to create proxy objects.
        /// </summary>
        public IProxyFactory OnlineProxyFactory
        {
            get { return onlineProxyFactory; }
            set { onlineProxyFactory = value; }
        }

        /// <summary>
        /// This method dispatches the given request by invoking the corresponding method.
        /// </summary>
        /// <param name="request">Request to be dispatched</param>
        /// <param name="networkName">Current network name</param>
        /// <returns></returns>
        public virtual DispatchResult Dispatch(Request request, string networkName)
        {
            object onlineProxy = null;

            try
            {
                onlineProxy = onlineProxyFactory.GetOnlineProxy(request, networkName);
            }
            catch (KeyNotFoundException)
            {
                // network doesn't have a url... retry later (not really an exception)
                return new DispatchResult(DispatchResultStatus.Failed, null);
            }
            catch (Exception ex)
            {
                DispatchResultStatus result = DispatchResultStatus.Failed;
                OnExceptionAction action = InvokeRequestException(request, ex);

                if (action == OnExceptionAction.Ignore)
                    result = DispatchResultStatus.Succeeded;

                return new DispatchResult(result, ex);
            }

            Exception exception = null;
            int retries = 0;

            while (retries++ <= request.Behavior.MaxRetries)
            {
                exception = null;
                try
                {
                    object result = InvokeOnlineProxyMethod(onlineProxy, request);

                    try
                    {
                        InvokeReturnCommand(onlineProxy, request, result);
                    }
                    catch (Exception ex)
                    {
                        //Make sure opnly ReturnCallbackExceptions are thrown from here
                        Exception realException = ex;
                        if (realException is TargetInvocationException && realException.InnerException != null)
                            realException = realException.InnerException;

                        if (realException is ReturnCallbackException)
                            throw realException;
                        else
                            throw new ReturnCallbackException(Resources.ExceptionOnReturnCallback + realException.Message, realException);
                    }
                    return new DispatchResult(DispatchResultStatus.Succeeded, null);
                }
                catch (CancelDispatchException)
                {
                    throw;
                }
                catch (Exception ex)
                {
                    exception = ex;
                    if (ex is TargetInvocationException)
                        exception = ex.InnerException;

                    OnExceptionAction action = InvokeRequestException(request, exception);

                    if (action != OnExceptionAction.Retry)
                    {
                        if (action == OnExceptionAction.Dismiss)
                            return new DispatchResult(DispatchResultStatus.Failed, exception);
                        else
                            return new DispatchResult(DispatchResultStatus.Succeeded, exception);
                    }

                }
            }

            return new DispatchResult(DispatchResultStatus.Failed, exception);
        }

        //private DispatchResultStatus? InvokeRequestException(Request request, int retries, Exception exception)
        //{
        //    DispatchResultStatus? dispatchResult = DispatchResultStatus.Failed;
        //    try
        //    {
        //        //Invoke the exception callback
        //        switch (InvokeExceptionCommand(request, exception))
        //        {
        //            case OnExceptionAction.Dismiss:
        //                dispatchResult = DispatchResultStatus.Failed;
        //                break;
        //            case OnExceptionAction.Retry:
        //                if (retries >= request.Behavior.MaxRetries)
        //                    dispatchResult = DispatchResultStatus.Failed;
        //                else
        //                    dispatchResult = null;
        //                break;
        //            case OnExceptionAction.Ignore:
        //                dispatchResult = DispatchResultStatus.Succeeded;
        //                break;
        //        }
        //    }
        //    catch
        //    {
        //    }

        //    return dispatchResult;
        //}

        private OnExceptionAction InvokeRequestException(Request request, Exception exception)
        {
            OnExceptionAction result = OnExceptionAction.Dismiss;
            try
            {
                result = InvokeExceptionCommand(request, exception);
            }
            catch
            {
            }
            return result;
        }


        virtual protected OnExceptionAction InvokeExceptionCommand(Request request, Exception realException)
        {
            if (request.Behavior.ExceptionCallback != null)
            {
                return (OnExceptionAction)request.Behavior.ExceptionCallback.Invoke(request, realException);
            }
            return OnExceptionAction.Retry;
        }

        virtual protected void InvokeReturnCommand(object onlineProxy, Request request, object result)
        {
            if (request.Behavior.ReturnCallback != null)
            {
                MethodInfo method = onlineProxy.GetType().GetMethod(request.MethodName);
                if (method.ReturnType != typeof(void))
                    request.Behavior.ReturnCallback.Invoke(request, request.CallParameters, result);
                else
                    request.Behavior.ReturnCallback.Invoke(request, request.CallParameters);
            }
        }

        virtual protected object InvokeOnlineProxyMethod(object onlineProxy, Request request)
        {
            MethodInfo method = onlineProxy.GetType().GetMethod(request.MethodName);
            object result = null;

            //Set the MessageID soapheader for Idempotency conforming WSAddressing
            PropertyInfo property = onlineProxy.GetType().GetProperty("MessageIDValue");
            if (property != null)
            {
                object reqId = Activator.CreateInstance(property.PropertyType);
                PropertyInfo textProperty = reqId.GetType().GetProperty("Text");
                textProperty.SetValue(reqId, new string[] { "uuid:" + request.Behavior.MessageId.ToString() }, null);
                property.SetValue(onlineProxy, reqId, null);
            }

            result = method.Invoke(onlineProxy, request.CallParameters);
            return result;
        }
    }
}
